/*
* Copyright 2016-present Facebook, Inc.
*
* 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.facebook.buck.shell;
import com.facebook.buck.log.Logger;
import com.facebook.buck.util.concurrent.LinkedBlockingStack;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashCode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
public abstract class WorkerProcessPool {
private static final Logger LOG = Logger.get(WorkerProcessPool.class);
private final int capacity;
private final BlockingQueue<WorkerProcess> availableWorkers;
@GuardedBy("createdWorkers")
private final List<WorkerProcess> createdWorkers;
private final HashCode poolHash;
public WorkerProcessPool(int maxWorkers, HashCode poolHash) {
this.capacity = maxWorkers;
this.availableWorkers = new LinkedBlockingStack<>();
this.createdWorkers = new ArrayList<>();
this.poolHash = poolHash;
}
/**
* If there are available workers, returns one. Otherwise blocks until one becomes available and
* returns it. You must free worker process by calling {@link #returnWorkerProcess(WorkerProcess)}
* or {@link #destroyWorkerProcess(WorkerProcess)} methods after you finish using it.
*/
public WorkerProcess borrowWorkerProcess() throws IOException, InterruptedException {
WorkerProcess workerProcess;
while ((workerProcess = availableWorkers.poll(0, TimeUnit.SECONDS)) != null) {
if (workerProcess.isAlive()) {
break;
}
try {
destroyWorkerProcess(workerProcess);
} catch (Exception ex) {
LOG.error(ex, "Failed to close dead worker process; ignoring.");
}
}
if (workerProcess == null) {
workerProcess = createNewWorkerIfPossible();
}
if (workerProcess != null) {
return workerProcess;
}
return availableWorkers.take();
}
private @Nullable WorkerProcess createNewWorkerIfPossible() throws IOException {
synchronized (createdWorkers) {
if (createdWorkers.size() == capacity) {
return null;
}
WorkerProcess process = Preconditions.checkNotNull(startWorkerProcess());
createdWorkers.add(process);
return process;
}
}
public void returnWorkerProcess(WorkerProcess workerProcess) {
synchronized (createdWorkers) {
Preconditions.checkArgument(
createdWorkers.contains(workerProcess),
"Trying to return a foreign WorkerProcess to the pool");
}
// Note: put() can throw, offer doesn't.
boolean added = availableWorkers.offer(workerProcess);
Preconditions.checkState(added, "Should have had enough room for existing worker");
}
// Same as returnWorkerProcess, except this assumes the worker is borked and should be terminated
// with prejudice.
public void destroyWorkerProcess(WorkerProcess workerProcess) {
synchronized (createdWorkers) {
boolean removed = createdWorkers.remove(workerProcess);
Preconditions.checkArgument(removed, "Trying to return a foreign WorkerProcess to the pool");
}
workerProcess.close();
}
public void close() {
ImmutableSet<WorkerProcess> processesToClose;
synchronized (createdWorkers) {
processesToClose = ImmutableSet.copyOf(createdWorkers);
Preconditions.checkState(
availableWorkers.size() == createdWorkers.size(),
"WorkerProcessPool was still running when shutdown was called.");
}
Exception ex = null;
for (WorkerProcess process : processesToClose) {
try {
process.close();
} catch (Exception t) {
ex = t;
}
}
if (ex != null) {
throw new RuntimeException(ex);
}
}
public int getCapacity() {
return capacity;
}
protected abstract WorkerProcess startWorkerProcess() throws IOException;
public HashCode getPoolHash() {
return poolHash;
}
}