org.gradle.launcher.daemon.client.DefaultDaemonConnector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.launcher.daemon.client;
import com.google.common.base.Preconditions;
import org.gradle.api.internal.specs.ExplainingSpec;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.specs.Spec;
import org.gradle.internal.Pair;
import org.gradle.internal.time.CountdownTimer;
import org.gradle.internal.time.TimeProvider;
import org.gradle.internal.time.Timers;
import org.gradle.internal.time.TrueTimeProvider;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.logging.progress.ProgressLogger;
import org.gradle.internal.logging.progress.ProgressLoggerFactory;
import org.gradle.internal.remote.internal.ConnectException;
import org.gradle.internal.remote.internal.OutgoingConnector;
import org.gradle.internal.remote.internal.RemoteConnection;
import org.gradle.internal.serialize.Serializers;
import org.gradle.launcher.daemon.context.DaemonConnectDetails;
import org.gradle.launcher.daemon.context.DaemonContext;
import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
import org.gradle.launcher.daemon.logging.DaemonMessages;
import org.gradle.launcher.daemon.protocol.DaemonMessageSerializer;
import org.gradle.launcher.daemon.protocol.Message;
import org.gradle.launcher.daemon.registry.DaemonInfo;
import org.gradle.launcher.daemon.registry.DaemonRegistry;
import org.gradle.launcher.daemon.registry.DaemonStopEvent;
import org.gradle.launcher.daemon.registry.DaemonStopEvents;
import org.gradle.launcher.daemon.server.api.DaemonStateControl;
import org.gradle.util.CollectionUtils;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import static java.lang.Thread.sleep;
import static org.gradle.launcher.daemon.server.api.DaemonStateControl.State.Canceled;
import static org.gradle.launcher.daemon.server.api.DaemonStateControl.State.Idle;
/**
* Provides the mechanics of connecting to a daemon, starting one via a given runnable if no suitable daemons are already available.
*/
public class DefaultDaemonConnector implements DaemonConnector {
private static final Logger LOGGER = Logging.getLogger(DefaultDaemonConnector.class);
public static final int DEFAULT_CONNECT_TIMEOUT = 30000;
public static final int CANCELED_WAIT_TIMEOUT = 3000;
private final DaemonRegistry daemonRegistry;
protected final OutgoingConnector connector;
private final DaemonStarter daemonStarter;
private final DaemonStartListener startListener;
private final ProgressLoggerFactory progressLoggerFactory;
private final TimeProvider timeProvider = new TrueTimeProvider();
private long connectTimeout = DefaultDaemonConnector.DEFAULT_CONNECT_TIMEOUT;
public DefaultDaemonConnector(DaemonRegistry daemonRegistry, OutgoingConnector connector, DaemonStarter daemonStarter, DaemonStartListener startListener, ProgressLoggerFactory progressLoggerFactory) {
Preconditions.checkNotNull(daemonRegistry);
Preconditions.checkNotNull(connector);
Preconditions.checkNotNull(daemonStarter);
Preconditions.checkNotNull(startListener);
Preconditions.checkNotNull(progressLoggerFactory);
this.daemonRegistry = daemonRegistry;
this.connector = connector;
this.daemonStarter = daemonStarter;
this.startListener = startListener;
this.progressLoggerFactory = progressLoggerFactory;
}
public void setConnectTimeout(long connectTimeout) {
this.connectTimeout = connectTimeout;
}
public long getConnectTimeout() {
return connectTimeout;
}
public DaemonRegistry getDaemonRegistry() {
return daemonRegistry;
}
public DaemonClientConnection maybeConnect(ExplainingSpec constraint) {
return findConnection(getCompatibleDaemons(daemonRegistry.getAll(), constraint));
}
public DaemonClientConnection maybeConnect(DaemonConnectDetails daemon) {
try {
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
} catch (ConnectException e) {
LOGGER.debug("Cannot connect to daemon {} due to {}. Ignoring.", daemon, e);
}
return null;
}
public DaemonClientConnection connect(ExplainingSpec constraint) {
final Pair, Collection> idleBusy = partitionByState(daemonRegistry.getAll(), Idle);
final Collection idleDaemons = idleBusy.getLeft();
final Collection busyDaemons = idleBusy.getRight();
// Check to see if there are any compatible idle daemons
DaemonClientConnection connection = connectToIdleDaemon(idleDaemons, constraint);
if (connection != null) {
return connection;
}
// Check to see if there are any compatible canceled daemons and wait to see if one becomes idle
connection = connectToCanceledDaemon(busyDaemons, constraint);
if (connection != null) {
return connection;
}
// No compatible daemons available - start a new daemon
handleStopEvents(idleDaemons, busyDaemons);
return startDaemon(constraint);
}
private void handleStopEvents(Collection idleDaemons, Collection busyDaemons) {
final List stopEvents = daemonRegistry.getStopEvents();
// Clean up old stop events
daemonRegistry.removeStopEvents(DaemonStopEvents.oldStopEvents(stopEvents));
final List recentStopEvents = DaemonStopEvents.uniqueRecentDaemonStopEvents(stopEvents);
for (DaemonStopEvent stopEvent : recentStopEvents) {
Long pid = stopEvent.getPid();
LOGGER.info("Previous Daemon (" + (pid == null ? "PID unknown" : pid) + ") stopped at " + stopEvent.getTimestamp() + " " + stopEvent.getReason());
}
LOGGER.lifecycle(DaemonStartupMessage.generate(busyDaemons.size(), idleDaemons.size(), recentStopEvents.size()));
}
private DaemonClientConnection connectToIdleDaemon(Collection idleDaemons, ExplainingSpec constraint) {
final List compatibleIdleDaemons = getCompatibleDaemons(idleDaemons, constraint);
return findConnection(compatibleIdleDaemons);
}
private DaemonClientConnection connectToCanceledDaemon(Collection busyDaemons, ExplainingSpec constraint) {
DaemonClientConnection connection = null;
final Pair, Collection> canceledBusy = partitionByState(busyDaemons, Canceled);
final Collection compatibleCanceledDaemons = getCompatibleDaemons(canceledBusy.getLeft(), constraint);
if (!compatibleCanceledDaemons.isEmpty()) {
LOGGER.info(DaemonMessages.WAITING_ON_CANCELED);
CountdownTimer timer = Timers.startTimer(CANCELED_WAIT_TIMEOUT);
while (connection == null && !timer.hasExpired()) {
try {
sleep(200);
connection = connectToIdleDaemon(daemonRegistry.getIdle(), constraint);
} catch (InterruptedException e) {
throw UncheckedException.throwAsUncheckedException(e);
}
}
}
return connection;
}
private Pair, Collection> partitionByState(final Collection daemons, final DaemonStateControl.State state) {
return CollectionUtils.partition(daemons, new Spec() {
public boolean isSatisfiedBy(DaemonInfo daemonInfo) {
return daemonInfo.getState() == state;
}
});
}
private List getCompatibleDaemons(Iterable daemons, ExplainingSpec constraint) {
List compatibleDaemons = new LinkedList();
for (DaemonInfo daemon : daemons) {
if (constraint.isSatisfiedBy(daemon.getContext())) {
compatibleDaemons.add(daemon);
} else {
LOGGER.info("Found daemon {} however its context does not match the desired criteria.\n"
+ constraint.whyUnsatisfied(daemon.getContext()) + "\n"
+ " Looking for a different daemon...", daemon);
}
}
return compatibleDaemons;
}
private DaemonClientConnection findConnection(List compatibleDaemons) {
for (DaemonInfo daemon : compatibleDaemons) {
try {
return connectToDaemon(daemon, new CleanupOnStaleAddress(daemon, true));
} catch (ConnectException e) {
LOGGER.debug("Cannot connect to daemon {} due to {}. Trying a different daemon...", daemon, e);
}
}
return null;
}
public DaemonClientConnection startDaemon(ExplainingSpec constraint) {
ProgressLogger progressLogger = progressLoggerFactory.newOperation(DefaultDaemonConnector.class)
.start("Starting Gradle Daemon", "Starting Daemon");
final DaemonStartupInfo startupInfo = daemonStarter.startDaemon();
LOGGER.debug("Started Gradle daemon {}", startupInfo);
CountdownTimer timer = Timers.startTimer(connectTimeout);
try {
do {
DaemonClientConnection daemonConnection = connectToDaemonWithId(startupInfo, constraint);
if (daemonConnection != null) {
startListener.daemonStarted(daemonConnection.getDaemon());
return daemonConnection;
}
try {
sleep(200L);
} catch (InterruptedException e) {
throw UncheckedException.throwAsUncheckedException(e);
}
} while (!timer.hasExpired());
} finally {
progressLogger.completed();
}
throw new DaemonConnectionException("Timeout waiting to connect to the Gradle daemon.\n" + startupInfo.describe());
}
private DaemonClientConnection connectToDaemonWithId(DaemonStartupInfo daemon, ExplainingSpec constraint) throws ConnectException {
// Look for 'our' daemon among the busy daemons - a daemon will start in busy state so that nobody else will grab it.
for (DaemonInfo daemonInfo : daemonRegistry.getNotIdle()) {
if (daemonInfo.getUid().equals(daemon.getUid())) {
try {
if (!constraint.isSatisfiedBy(daemonInfo.getContext())) {
throw new DaemonConnectionException("The newly created daemon process has a different context than expected."
+ "\nIt won't be possible to reconnect to this daemon. Context mismatch: "
+ "\n" + constraint.whyUnsatisfied(daemonInfo.getContext()));
}
return connectToDaemon(daemonInfo, new CleanupOnStaleAddress(daemonInfo, false));
} catch (ConnectException e) {
throw new DaemonConnectionException("Could not connect to the Gradle daemon.\n" + daemon.describe(), e);
}
}
}
return null;
}
private DaemonClientConnection connectToDaemon(DaemonConnectDetails daemon, DaemonClientConnection.StaleAddressDetector staleAddressDetector) throws ConnectException {
ProgressLogger progressLogger = progressLoggerFactory.newOperation(DefaultDaemonConnector.class)
.start("Connecting to Gradle Daemon", "Connecting to Daemon");
RemoteConnection connection;
try {
connection = connector.connect(daemon.getAddress()).create(Serializers.stateful(DaemonMessageSerializer.create()));
} catch (ConnectException e) {
staleAddressDetector.maybeStaleAddress(e);
throw e;
} finally {
progressLogger.completed();
}
return new DaemonClientConnection(connection, daemon, staleAddressDetector);
}
private class CleanupOnStaleAddress implements DaemonClientConnection.StaleAddressDetector {
private final DaemonConnectDetails daemon;
private final boolean exposeAsStale;
public CleanupOnStaleAddress(DaemonConnectDetails daemon, boolean exposeAsStale) {
this.daemon = daemon;
this.exposeAsStale = exposeAsStale;
}
public boolean maybeStaleAddress(Exception failure) {
LOGGER.info("{}{}", DaemonMessages.REMOVING_DAEMON_ADDRESS_ON_FAILURE, daemon);
final Date timestamp = new Date(System.currentTimeMillis());
final DaemonStopEvent stopEvent = new DaemonStopEvent(timestamp, daemon.getPid(), null, "by user or operating system");
daemonRegistry.storeStopEvent(stopEvent);
daemonRegistry.remove(daemon.getAddress());
return exposeAsStale;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy