com.android.builder.png.QueuedCruncher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of builder Show documentation
Show all versions of builder Show documentation
Library to build Android applications.
/*
* 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.PngCruncher;
import com.android.ide.common.internal.PngException;
import com.android.utils.ILogger;
import com.google.common.base.Objects;
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;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 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 sInstances =
new ConcurrentHashMap();
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) {
logger.info("QueuedCruncher is using %1$s", aaptLocation);
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 mCrunchingRequests;
// list of outstanding jobs.
@NonNull private final Map>> mOutstandingJobs =
new ConcurrentHashMap>>();
// ref count of active users, if it drops to zero, that means there are no more active users
// and the queue should be shutdown.
@NonNull private final AtomicInteger refCount = new AtomicInteger(0);
// per process unique key provider to remember which users enlisted which requests.
@NonNull private final AtomicInteger keyProvider = new AtomicInteger(0);
private QueuedCruncher(
@NonNull String aaptLocation,
@NonNull ILogger iLogger) {
mAaptLocation = aaptLocation;
mLogger = iLogger;
QueueThreadContext queueThreadContext = new QueueThreadContext() {
// move this to a TLS, but do not store instances of AaptProcess in it.
@NonNull private final Map mAaptProcesses =
new ConcurrentHashMap();
@Override
public void creation(@NonNull 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;
aaptProcess.waitForReady();
mAaptProcesses.put(t.getName(), aaptProcess);
} catch (InterruptedException e) {
mLogger.error(e, "Cannot start slave process");
e.printStackTrace();
}
}
@Override
public void runTask(@NonNull Job job) throws Exception {
job.runTask(
new JobContext(
mAaptProcesses.get(Thread.currentThread().getName())));
mOutstandingJobs.get(((QueuedJob) job).key).remove(job);
}
@Override
public void destruction(@NonNull 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 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(
mLogger, queueThreadContext, "png-cruncher", 5, 2f);
}
private static final class QueuedJob extends Job {
private final int key;
public QueuedJob(int key, String jobTile, Task task) {
super(jobTile, task);
this.key = key;
}
}
@Override
public void crunchPng(int key, @NonNull final File from, @NonNull final File to)
throws PngException {
try {
final Job aaptProcessJob = new QueuedJob(
key,
"Cruncher " + from.getName(),
new Task() {
@Override
public void run(@NonNull Job job,
@NonNull JobContext 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());
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("from", from.getAbsolutePath())
.add("to", to.getAbsolutePath())
.toString();
}
});
mOutstandingJobs.get(key).add(aaptProcessJob);
mCrunchingRequests.push(aaptProcessJob);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
throw new PngException(e);
}
}
private void waitForAll(int key) throws InterruptedException {
mLogger.verbose("Thread(%1$s): begin waitForAll", Thread.currentThread().getName());
ConcurrentLinkedQueue> jobs = mOutstandingJobs.get(key);
Job aaptProcessJob = jobs.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 = jobs.poll();
}
mLogger.verbose("Thread(%1$s): end waitForAll", Thread.currentThread().getName());
}
@Override
public synchronized int start() {
// increment our reference count.
refCount.incrementAndGet();
// get a unique key for the lifetime of this process.
int key = keyProvider.incrementAndGet();
mOutstandingJobs.put(key, new ConcurrentLinkedQueue>());
return key;
}
@Override
public synchronized void end(int key) throws InterruptedException {
long startTime = System.currentTimeMillis();
try {
waitForAll(key);
mOutstandingJobs.get(key).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.
if (refCount.decrementAndGet() == 0) {
mCrunchingRequests.shutdown();
mLogger.verbose("Shutdown finished in %1$d",
System.currentTimeMillis() - startTime);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy