/* * Copyright (C) 2014 The Android Open Source Project * * 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 com.android.builder.png; import com.android.annotations.NonNull; import com.android.builder.tasks.Job; import com.android.builder.tasks.JobContext; import com.android.builder.tasks.QueueThreadContext; import com.android.builder.tasks.Task; import com.android.builder.tasks.WorkQueue; import com.android.ide.common.internal.LoggedErrorException; import com.android.ide.common.internal.PngCruncher; import com.android.utils.ILogger; import com.google.common.collect.ImmutableList; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** * implementation of {@link com.android.ide.common.internal.PngCruncher} that queues request and * use a pool or aapt server processes to serve those. */ public class QueuedCruncher implements PngCruncher { // use an enum to ensure singleton. public enum Builder { INSTANCE; private final Map<String, QueuedCruncher> sInstances = new ConcurrentHashMap<String, QueuedCruncher>(); private final Object sLock = new Object(); /** * Creates a new {@link com.android.builder.png.QueuedCruncher} or return an existing one * based on the underlying AAPT executable location. * @param aaptLocation the APPT executable location. * @param logger the logger to use * @return a new of existing instance of the {@link com.android.builder.png.QueuedCruncher} */ public QueuedCruncher newCruncher( @NonNull String aaptLocation, @NonNull ILogger logger) { synchronized (sLock) { if (!sInstances.containsKey(aaptLocation)) { QueuedCruncher queuedCruncher = new QueuedCruncher(aaptLocation, logger); sInstances.put(aaptLocation, queuedCruncher); } return sInstances.get(aaptLocation); } } } @NonNull private final String mAaptLocation; @NonNull private final ILogger mLogger; // Queue responsible for handling all passed jobs with a pool of worker threads. @NonNull private final WorkQueue<AaptProcess> mCrunchingRequests; // list of outstanding jobs. @NonNull private final ConcurrentLinkedQueue<Job<AaptProcess>> mOutstandingJobs = new ConcurrentLinkedQueue<Job<AaptProcess>>(); private QueuedCruncher( @NonNull String aaptLocation, @NonNull ILogger iLogger) { mAaptLocation = aaptLocation; mLogger = iLogger; QueueThreadContext<AaptProcess> queueThreadContext = new QueueThreadContext<AaptProcess>() { // move this to a TLS. @NonNull private final Map<String, AaptProcess> mAaptProcesses = new HashMap<String, AaptProcess>(); @Override public void creation(Thread t) throws IOException { try { mLogger.verbose("Thread(%1$s): create aapt slave", Thread.currentThread().getName()); AaptProcess aaptProcess = new AaptProcess.Builder(mAaptLocation, mLogger).start(); assert aaptProcess != null; mAaptProcesses.put(t.getName(), aaptProcess); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void runTask(Job<AaptProcess> job) throws Exception { job.runTask( new JobContext<AaptProcess>(mAaptProcesses.get(Thread.currentThread().getName()))); } @Override public void destruction(Thread t) throws IOException, InterruptedException { AaptProcess aaptProcess = mAaptProcesses.get(Thread.currentThread().getName()); if (aaptProcess != null) { mLogger.verbose("Thread(%1$s): notify aapt slave shutdown", Thread.currentThread().getName()); aaptProcess.shutdown(); mAaptProcesses.remove(t.getName()); mLogger.verbose("Thread(%1$s): after shutdown queue_size=%2$d", Thread.currentThread().getName(), mAaptProcesses.size()); } } @Override public void shutdown() { if (!mAaptProcesses.isEmpty()) { mLogger.warning("Process list not empty"); for (Map.Entry<String, AaptProcess> aaptProcessEntry : mAaptProcesses .entrySet()) { mLogger.warning("Thread(%1$s): queue not cleaned", aaptProcessEntry.getKey()); try { aaptProcessEntry.getValue().shutdown(); } catch (Exception e) { mLogger.error(e, "while shutting down" + aaptProcessEntry.getKey()); } } } mAaptProcesses.clear(); } }; mCrunchingRequests = new WorkQueue<AaptProcess>(mLogger, queueThreadContext, "png-cruncher", 5, 2); } @Override public void crunchPng(@NonNull final File from, @NonNull final File to) throws InterruptedException, LoggedErrorException, IOException { final Job<AaptProcess> aaptProcessJob = new Job<AaptProcess>( "Cruncher " + from.getName(), new Task<AaptProcess>() { @Override public void run(Job<AaptProcess> job, JobContext<AaptProcess> context) throws IOException { mLogger.verbose("Thread(%1$s): begin executing job %2$s", Thread.currentThread().getName(), job.getJobTitle()); context.getPayload().crunch(from, to, job); mLogger.verbose("Thread(%1$s): done executing job %2$s", Thread.currentThread().getName(), job.getJobTitle()); } }); mOutstandingJobs.add(aaptProcessJob); mCrunchingRequests.push(aaptProcessJob); } public void waitForAll() throws InterruptedException { mLogger.verbose("Thread(%1$s): begin waitForAll", Thread.currentThread().getName()); Job<AaptProcess> aaptProcessJob = mOutstandingJobs.poll(); while (aaptProcessJob != null) { mLogger.verbose("Thread(%1$s) : wait for {%2$s)", Thread.currentThread().getName(), aaptProcessJob.toString()); if (!aaptProcessJob.await()) { throw new RuntimeException( "Crunching " + aaptProcessJob.getJobTitle() + " failed, see logs"); } aaptProcessJob = mOutstandingJobs.poll(); } mLogger.verbose("Thread(%1$s): end waitForAll", Thread.currentThread().getName()); } @Override public void end() throws InterruptedException { long startTime = System.currentTimeMillis(); try { waitForAll(); mOutstandingJobs.clear(); mLogger.verbose("Job finished in %1$d", System.currentTimeMillis() - startTime); } finally { // even if we have failures, we need to shutdown property the sub processes. mCrunchingRequests.shutdown(); mLogger.verbose("Shutdown finished in %1$d", System.currentTimeMillis() - startTime); } } }