Skip to content

Commit

Permalink
Move some interactive prompt handling from the daemon to the client (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
bot-gradle committed Feb 22, 2024
2 parents 6a7d810 + dfd0b7f commit e2ff64e
Show file tree
Hide file tree
Showing 66 changed files with 1,154 additions and 722 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import org.gradle.internal.concurrent.CompositeStoppable;
import org.gradle.internal.concurrent.Stoppable;
import org.gradle.internal.jvm.inspection.JvmVersionDetector;
import org.gradle.internal.logging.events.OutputEventListener;
import org.gradle.internal.nativeintegration.services.NativeServices;
import org.gradle.internal.service.ServiceRegistry;
import org.gradle.internal.service.ServiceRegistryBuilder;
Expand Down Expand Up @@ -117,22 +116,22 @@ public Action<? super ExecutionListener> createAction(CommandLineParser parser,

private Runnable stopAllDaemons(DaemonParameters daemonParameters) {
ServiceRegistry clientSharedServices = createGlobalClientServices(false);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createMessageDaemonServices(loggingServices.get(OutputEventListener.class), daemonParameters);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createMessageDaemonServices(loggingServices, daemonParameters);
DaemonStopClient stopClient = clientServices.get(DaemonStopClient.class);
return new StopDaemonAction(stopClient);
}

private Runnable showDaemonStatus(DaemonParameters daemonParameters) {
ServiceRegistry clientSharedServices = createGlobalClientServices(false);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createMessageDaemonServices(loggingServices.get(OutputEventListener.class), daemonParameters);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createMessageDaemonServices(loggingServices, daemonParameters);
ReportDaemonStatusClient statusClient = clientServices.get(ReportDaemonStatusClient.class);
return new ReportDaemonStatusAction(statusClient);
}

private Runnable runBuildWithDaemon(StartParameterInternal startParameter, DaemonParameters daemonParameters) {
// Create a client that will match based on the daemon startup parameters.
ServiceRegistry clientSharedServices = createGlobalClientServices(true);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createBuildClientServices(loggingServices.get(OutputEventListener.class), daemonParameters, System.in);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createBuildClientServices(loggingServices, daemonParameters, System.in);
DaemonClient client = clientServices.get(DaemonClient.class);
return runBuildAndCloseServices(startParameter, daemonParameters, client, clientSharedServices, clientServices);
}
Expand Down Expand Up @@ -168,9 +167,9 @@ private Runnable runBuildInSingleUseDaemon(StartParameterInternal startParameter
}
//end of workaround.

// Create a client that will not match any existing daemons, so it will always startup a new one
// Create a client that will not match any existing daemons, so it will always start a new one
ServiceRegistry clientSharedServices = createGlobalClientServices(true);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createSingleUseDaemonClientServices(clientSharedServices.get(OutputEventListener.class), daemonParameters, System.in);
ServiceRegistry clientServices = clientSharedServices.get(DaemonClientFactory.class).createSingleUseDaemonClientServices(clientSharedServices, daemonParameters, System.in);
DaemonClient client = clientServices.get(DaemonClient.class);
return runBuildAndCloseServices(startParameter, daemonParameters, client, clientSharedServices, clientServices);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.gradle.internal.id.IdGenerator;
import org.gradle.internal.invocation.BuildAction;
import org.gradle.internal.logging.ConsoleRenderer;
import org.gradle.internal.logging.console.GlobalUserInputReceiver;
import org.gradle.internal.logging.events.OutputEventListener;
import org.gradle.internal.nativeintegration.ProcessEnvironment;
import org.gradle.internal.remote.internal.Connection;
Expand Down Expand Up @@ -100,18 +101,28 @@ public class DaemonClient implements BuildActionExecuter<BuildActionParameters,
private final OutputEventListener outputEventListener;
private final ExplainingSpec<DaemonContext> compatibilitySpec;
private final InputStream buildStandardInput;
private final GlobalUserInputReceiver userInput;
private final ExecutorFactory executorFactory;
private final IdGenerator<UUID> idGenerator;
private final ProcessEnvironment processEnvironment;

//TODO - outputEventListener and buildStandardInput are per-build settings
//so down the road we should refactor the code accordingly and potentially attach them to BuildActionParameters
public DaemonClient(DaemonConnector connector, OutputEventListener outputEventListener, ExplainingSpec<DaemonContext> compatibilitySpec,
InputStream buildStandardInput, ExecutorFactory executorFactory, IdGenerator<UUID> idGenerator, ProcessEnvironment processEnvironment) {
public DaemonClient(
DaemonConnector connector,
OutputEventListener outputEventListener,
ExplainingSpec<DaemonContext> compatibilitySpec,
InputStream buildStandardInput,
GlobalUserInputReceiver userInput,
ExecutorFactory executorFactory,
IdGenerator<UUID> idGenerator,
ProcessEnvironment processEnvironment
) {
this.connector = connector;
this.outputEventListener = outputEventListener;
this.compatibilitySpec = compatibilitySpec;
this.buildStandardInput = buildStandardInput;
this.userInput = userInput;
this.executorFactory = executorFactory;
this.idGenerator = idGenerator;
this.processEnvironment = processEnvironment;
Expand Down Expand Up @@ -228,7 +239,7 @@ protected BuildActionResult executeBuild(Build build, DaemonClientConnection con
}

private Object monitorBuild(Build build, DaemonDiagnostics diagnostics, Connection<Message> connection, BuildCancellationToken cancellationToken, BuildEventConsumer buildEventConsumer) {
DaemonClientInputForwarder inputForwarder = new DaemonClientInputForwarder(buildStandardInput, connection, executorFactory);
DaemonClientInputForwarder inputForwarder = new DaemonClientInputForwarder(buildStandardInput, connection, userInput, executorFactory);
DaemonCancelForwarder cancelForwarder = new DaemonCancelForwarder(connection, cancellationToken);
try {
cancelForwarder.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package org.gradle.launcher.daemon.client;

import org.gradle.internal.logging.console.GlobalUserInputReceiver;
import org.gradle.internal.logging.events.OutputEventListener;
import org.gradle.internal.service.DefaultServiceRegistry;
import org.gradle.internal.service.ServiceLookup;
import org.gradle.internal.service.ServiceRegistry;
import org.gradle.launcher.daemon.configuration.DaemonParameters;

Expand All @@ -34,31 +36,35 @@ public DaemonClientFactory(ServiceRegistry sharedServices) {
/**
* Creates the services for a {@link DaemonClient} that can be used to run builds.
*/
public ServiceRegistry createBuildClientServices(OutputEventListener loggingReceiver, DaemonParameters daemonParameters, InputStream stdin) {
DefaultServiceRegistry loggingServices = new DefaultServiceRegistry(sharedServices);
loggingServices.add(OutputEventListener.class, loggingReceiver);
public ServiceRegistry createBuildClientServices(ServiceLookup clientLoggingServices, DaemonParameters daemonParameters, InputStream stdin) {
ServiceRegistry loggingServices = createLoggingServices(clientLoggingServices);
return new DaemonClientServices(loggingServices, daemonParameters, stdin);
}

/**
* Creates the services for a {@link DaemonClient} that can be used to run a build in a single-use daemon.
*/
public ServiceRegistry createSingleUseDaemonClientServices(OutputEventListener loggingReceiver, DaemonParameters daemonParameters, InputStream stdin) {
DefaultServiceRegistry loggingServices = new DefaultServiceRegistry(sharedServices);
loggingServices.add(OutputEventListener.class, loggingReceiver);
public ServiceRegistry createSingleUseDaemonClientServices(ServiceLookup clientLoggingServices, DaemonParameters daemonParameters, InputStream stdin) {
ServiceRegistry loggingServices = createLoggingServices(clientLoggingServices);
return new SingleUseDaemonClientServices(loggingServices, daemonParameters, stdin);
}

private DefaultServiceRegistry createLoggingServices(ServiceLookup clientLoggingServices) {
// Need to use some specific logging services from the client-specific registry, rather than the global registry
DefaultServiceRegistry loggingServices = new DefaultServiceRegistry(sharedServices);
loggingServices.add(OutputEventListener.class, clientLoggingServices.get(OutputEventListener.class));
loggingServices.add(GlobalUserInputReceiver.class, clientLoggingServices.get(GlobalUserInputReceiver.class));
return loggingServices;
}

/**
* Creates the services for sending simple messages to daemons.
*
* Currently, there are two clients which can be used from this registry:
* - {@link DaemonStopClient} that can be used to stop daemons.
* - {@link NotifyDaemonAboutChangedPathsClient} that can be used to notify daemons about changed paths.
* - {@link DaemonStopClient} that can be used to stop daemons.
* - {@link NotifyDaemonAboutChangedPathsClient} that can be used to notify daemons about changed paths.
*/
public ServiceRegistry createMessageDaemonServices(OutputEventListener loggingReceiver, DaemonParameters daemonParameters) {
DefaultServiceRegistry loggingServices = new DefaultServiceRegistry(sharedServices);
loggingServices.add(OutputEventListener.class, loggingReceiver);
return new DaemonClientServices(loggingServices, daemonParameters, new ByteArrayInputStream(new byte[0]));
public ServiceRegistry createMessageDaemonServices(ServiceLookup clientLoggingServices, DaemonParameters daemonParameters) {
return createBuildClientServices(clientLoggingServices, daemonParameters, new ByteArrayInputStream(new byte[0]));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,48 @@
import org.gradle.internal.concurrent.Stoppable;
import org.gradle.internal.dispatch.Dispatch;
import org.gradle.internal.io.TextStream;
import org.gradle.internal.logging.console.GlobalUserInputReceiver;
import org.gradle.internal.logging.console.UserInputReceiver;
import org.gradle.launcher.daemon.protocol.CloseInput;
import org.gradle.launcher.daemon.protocol.ForwardInput;
import org.gradle.launcher.daemon.protocol.InputMessage;
import org.gradle.launcher.daemon.protocol.UserResponse;

import javax.annotation.Nullable;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Eagerly consumes from an input stream, sending line by line ForwardInput
* commands over the connection and finishing with a CloseInput command.
* It also listens to cancel requests and forwards it too as Cancel command.
* Eagerly consumes from an input stream, sending each line as a commands over the connection and finishing with a {@link CloseInput} command.
* Each line is forwarded as either a {@link ForwardInput}, for non-interactive text, or a {@link UserResponse}, when expecting some interactive text.
*/
public class DaemonClientInputForwarder implements Stoppable {
private static final Logger LOGGER = Logging.getLogger(DaemonClientInputForwarder.class);

public static final int DEFAULT_BUFFER_SIZE = 8192;
private final InputForwarder forwarder;
private final GlobalUserInputReceiver userInput;

public DaemonClientInputForwarder(InputStream inputStream, Dispatch<? super InputMessage> dispatch,
ExecutorFactory executorFactory) {
this(inputStream, dispatch, executorFactory, DEFAULT_BUFFER_SIZE);
public DaemonClientInputForwarder(
InputStream inputStream,
Dispatch<? super InputMessage> dispatch,
GlobalUserInputReceiver userInput,
ExecutorFactory executorFactory
) {
this(inputStream, dispatch, userInput, executorFactory, DEFAULT_BUFFER_SIZE);
}

public DaemonClientInputForwarder(InputStream inputStream, Dispatch<? super InputMessage> dispatch,
ExecutorFactory executorFactory, int bufferSize) {
TextStream handler = new ForwardTextStreamToConnection(dispatch);
public DaemonClientInputForwarder(
InputStream inputStream,
Dispatch<? super InputMessage> dispatch,
GlobalUserInputReceiver userInput,
ExecutorFactory executorFactory,
int bufferSize
) {
this.userInput = userInput;
ForwardTextStreamToConnection handler = new ForwardTextStreamToConnection(dispatch);
forwarder = new InputForwarder(inputStream, handler, executorFactory, bufferSize);
userInput.dispatchTo(new ForwardingUserInput(handler));
}

public void start() {
Expand All @@ -56,22 +71,47 @@ public void start() {

@Override
public void stop() {
userInput.stopDispatching();
forwarder.stop();
}

private static class ForwardingUserInput implements UserInputReceiver {
private final ForwardTextStreamToConnection handler;

public ForwardingUserInput(ForwardTextStreamToConnection handler) {
this.handler = handler;
}

@Override
public void readAndForwardText() {
handler.forwardNextLineAsUserResponse();
}
}

private static class ForwardTextStreamToConnection implements TextStream {
private final Dispatch<? super InputMessage> dispatch;
private final AtomicBoolean forwardResponse = new AtomicBoolean();

public ForwardTextStreamToConnection(Dispatch<? super InputMessage> dispatch) {
this.dispatch = dispatch;
}

void forwardNextLineAsUserResponse() {
if (!forwardResponse.compareAndSet(false, true)) {
throw new IllegalStateException("Already expecting user input");
}
}

@Override
public void text(String input) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Forwarding input to daemon: '{}'", input.replace("\n", "\\n"));
}
dispatch.dispatch(new ForwardInput(input.getBytes()));
if (forwardResponse.compareAndSet(true, false)) {
dispatch.dispatch(new UserResponse(input));
} else {
dispatch.dispatch(new ForwardInput(input.getBytes()));
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.gradle.internal.id.IdGenerator;
import org.gradle.internal.id.UUIDGenerator;
import org.gradle.internal.invocation.BuildAction;
import org.gradle.internal.logging.console.GlobalUserInputReceiver;
import org.gradle.internal.logging.events.OutputEventListener;
import org.gradle.internal.logging.progress.DefaultProgressLoggerFactory;
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
Expand Down Expand Up @@ -80,6 +81,7 @@ protected DaemonClient createDaemonClient(IdGenerator<UUID> idGenerator) {
get(OutputEventListener.class),
matchingContextSpec,
buildStandardInput,
get(GlobalUserInputReceiver.class),
get(ExecutorFactory.class),
idGenerator,
get(ProcessEnvironment.class));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ public class InputForwarder implements Stoppable {
private final TextStream handler;
private final ExecutorFactory executorFactory;
private final int bufferSize;
private ManagedExecutor forwardingExecuter;
private ManagedExecutor forwardingExecutor;
private DisconnectableInputStream disconnectableInput;
private LineBufferingOutputStream outputBuffer;
private final Lock lifecycleLock = new ReentrantLock();
private boolean started;
private boolean stopped;

public InputForwarder(InputStream input, TextStream handler, ExecutorFactory executerFactory, int bufferSize) {
public InputForwarder(InputStream input, TextStream handler, ExecutorFactory executorFactory, int bufferSize) {
this.input = input;
this.handler = handler;
this.executorFactory = executerFactory;
this.executorFactory = executorFactory;
this.bufferSize = bufferSize;
}

Expand All @@ -68,8 +68,8 @@ public InputForwarder start() {
SystemProperties.getInstance().getLineSeparator(),
bufferSize);

forwardingExecuter = executorFactory.create("Forward input");
forwardingExecuter.execute(new Runnable() {
forwardingExecutor = executorFactory.create("Forward input");
forwardingExecutor.execute(new Runnable() {
@Override
public void run() {
byte[] buffer = new byte[bufferSize];
Expand Down Expand Up @@ -120,7 +120,7 @@ public void stop() {
throw UncheckedException.throwAsUncheckedException(e);
}

forwardingExecuter.stop();
forwardingExecutor.stop();
stopped = true;
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.gradle.internal.concurrent.ExecutorFactory;
import org.gradle.internal.id.IdGenerator;
import org.gradle.internal.invocation.BuildAction;
import org.gradle.internal.logging.console.GlobalUserInputReceiver;
import org.gradle.internal.logging.events.OutputEventListener;
import org.gradle.internal.nativeintegration.ProcessEnvironment;
import org.gradle.launcher.daemon.context.DaemonContext;
Expand All @@ -39,8 +40,18 @@ public class SingleUseDaemonClient extends DaemonClient {
private static final Logger LOGGER = Logging.getLogger(SingleUseDaemonClient.class);
private final DocumentationRegistry documentationRegistry;

public SingleUseDaemonClient(DaemonConnector connector, OutputEventListener outputEventListener, ExplainingSpec<DaemonContext> compatibilitySpec, InputStream buildStandardInput, ExecutorFactory executorFactory, IdGenerator<UUID> idGenerator, DocumentationRegistry documentationRegistry, ProcessEnvironment processEnvironment) {
super(connector, outputEventListener, compatibilitySpec, buildStandardInput, executorFactory, idGenerator, processEnvironment);
public SingleUseDaemonClient(
DaemonConnector connector,
OutputEventListener outputEventListener,
ExplainingSpec<DaemonContext> compatibilitySpec,
InputStream buildStandardInput,
GlobalUserInputReceiver userInput,
ExecutorFactory executorFactory,
IdGenerator<UUID> idGenerator,
DocumentationRegistry documentationRegistry,
ProcessEnvironment processEnvironment
) {
super(connector, outputEventListener, compatibilitySpec, buildStandardInput, userInput, executorFactory, idGenerator, processEnvironment);
this.documentationRegistry = documentationRegistry;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.gradle.api.internal.specs.ExplainingSpecs;
import org.gradle.internal.concurrent.ExecutorFactory;
import org.gradle.internal.id.IdGenerator;
import org.gradle.internal.logging.console.GlobalUserInputReceiver;
import org.gradle.internal.logging.events.OutputEventListener;
import org.gradle.internal.nativeintegration.ProcessEnvironment;
import org.gradle.internal.service.ServiceRegistry;
Expand All @@ -43,6 +44,7 @@ protected DaemonClient createDaemonClient(IdGenerator<UUID> idGenerator) {
get(OutputEventListener.class),
matchNone,
getBuildStandardInput(),
get(GlobalUserInputReceiver.class),
get(ExecutorFactory.class),
idGenerator,
get(DocumentationRegistry.class),
Expand Down

0 comments on commit e2ff64e

Please sign in to comment.