/* * Copyright 2012 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.server; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.internal.UncheckedException; import org.gradle.internal.concurrent.ExecutorFactory; import org.gradle.internal.concurrent.Stoppable; import org.gradle.internal.concurrent.StoppableExecutor; import org.gradle.internal.remote.internal.Connection; import org.gradle.internal.remote.internal.RemoteConnection; import org.gradle.launcher.daemon.context.DaemonContext; import org.gradle.launcher.daemon.logging.DaemonMessages; import org.gradle.launcher.daemon.protocol.Command; import org.gradle.launcher.daemon.protocol.Failure; import org.gradle.launcher.daemon.protocol.Message; import org.gradle.launcher.daemon.server.api.DaemonConnection; import org.gradle.launcher.daemon.server.api.DaemonStateControl; import org.gradle.launcher.daemon.server.exec.DaemonCommandExecuter; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class DefaultIncomingConnectionHandler implements IncomingConnectionHandler, Stoppable { private static final Logger LOGGER = Logging.getLogger(DefaultIncomingConnectionHandler.class); private final StoppableExecutor workers; private final byte[] token; private final DaemonContext daemonContext; private final DaemonCommandExecuter commandExecuter; private final DaemonStateControl daemonStateControl; private final ExecutorFactory executorFactory; private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private final Set<Connection<?>> inProgress = new HashSet<Connection<?>>(); public DefaultIncomingConnectionHandler(DaemonCommandExecuter commandExecuter, DaemonContext daemonContext, DaemonStateControl daemonStateControl, ExecutorFactory executorFactory, byte[] token) { this.commandExecuter = commandExecuter; this.daemonContext = daemonContext; this.daemonStateControl = daemonStateControl; this.executorFactory = executorFactory; workers = executorFactory.create("Daemon"); this.token = token; } public void handle(final RemoteConnection<Message> connection) { // Mark the connection has being handled onStartHandling(connection); //we're spinning a thread to do work to avoid blocking the connection //This means that the Daemon potentially can do multiple things but we only allows a single build at a time workers.execute(new ConnectionWorker(connection)); } private void onStartHandling(Connection<?> connection) { lock.lock(); try { inProgress.add(connection); } finally { lock.unlock(); } } private void onFinishHandling(Connection<?> connection) { lock.lock(); try { inProgress.remove(connection); condition.signalAll(); } finally { lock.unlock(); } } /** * Blocks until all connections have been handled or abandoned. */ public void stop() { lock.lock(); try { while (!inProgress.isEmpty()) { try { condition.await(); } catch (InterruptedException e) { throw UncheckedException.throwAsUncheckedException(e); } } } finally { lock.unlock(); } } private class ConnectionWorker implements Runnable { private final RemoteConnection<Message> connection; public ConnectionWorker(RemoteConnection<Message> connection) { this.connection = connection; } public void run() { try { receiveAndHandleCommand(); } finally { onFinishHandling(connection); } } private void receiveAndHandleCommand() { try { DefaultDaemonConnection daemonConnection = new DefaultDaemonConnection(connection, executorFactory); try { Command command = receiveCommand(daemonConnection); if (command != null) { handleCommand(command, daemonConnection); } } finally { daemonConnection.stop(); } } finally { connection.stop(); } } private Command receiveCommand(DaemonConnection daemonConnection) { try { Command command = (Command) daemonConnection.receive(120, TimeUnit.SECONDS); LOGGER.info("Received command: {}.", command); return command; } catch (Throwable e) { LOGGER.warn(String.format("Unable to receive command from client %s. Discarding connection.", connection), e); return null; } } private void handleCommand(Command command, DaemonConnection daemonConnection) { LOGGER.debug("{}{} with connection: {}.", DaemonMessages.STARTED_EXECUTING_COMMAND, command, connection); try { if (!Arrays.equals(command.getToken(), token)) { throw new BadlyFormedRequestException(String.format("Unexpected authentication token in command %s received from %s", command, connection)); } commandExecuter.executeCommand(daemonConnection, command, daemonContext, daemonStateControl); } catch (Throwable e) { LOGGER.warn(String.format("Unable to execute command %s from %s. Dispatching the failure to the daemon client", command, connection), e); daemonConnection.completed(new Failure(e)); } finally { LOGGER.debug("{}{}", DaemonMessages.FINISHED_EXECUTING_COMMAND, command); } Object finished = daemonConnection.receive(60, TimeUnit.SECONDS); LOGGER.debug("Received finished message: {}", finished); } } }