// Copyright (C) 2009 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.google.gerrit.server.patch; import static com.google.gerrit.server.patch.IntraLineLoader.log; import com.google.gerrit.server.config.GerritServerConfig; import com.google.inject.AbstractModule; import com.google.inject.Inject; import com.google.inject.Singleton; import org.eclipse.jgit.lib.Config; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @Singleton public class IntraLineWorkerPool { public static class Module extends AbstractModule { @Override protected void configure() { bind(IntraLineWorkerPool.class); } } private final BlockingQueue<Worker> workerPool; @Inject public IntraLineWorkerPool(@GerritServerConfig Config cfg) { int workers = cfg.getInt( "cache", PatchListCacheImpl.INTRA_NAME, "maxIdleWorkers", Runtime.getRuntime().availableProcessors() * 3 / 2); workerPool = new ArrayBlockingQueue<>(workers, true /* fair */); } Worker acquire() { Worker w = workerPool.poll(); if (w == null) { // If no worker is immediately available, start a new one. // Maximum parallelism is controlled by the web server. w = new Worker(); w.start(); } return w; } void release(Worker w) { if (!workerPool.offer(w)) { // If the idle worker pool is full, terminate the worker. w.shutdownGracefully(); } } static class Worker extends Thread { private static final AtomicInteger count = new AtomicInteger(1); private final ArrayBlockingQueue<Input> input; private final ArrayBlockingQueue<Result> result; Worker() { input = new ArrayBlockingQueue<>(1); result = new ArrayBlockingQueue<>(1); setName("IntraLineDiff-" + count.getAndIncrement()); setDaemon(true); } Result computeWithTimeout(IntraLineDiffKey key, long timeoutMillis) throws Exception { if (!input.offer(new Input(key))) { log.error("Cannot enqueue task to thread " + getName()); return Result.TIMEOUT; } Result r = result.poll(timeoutMillis, TimeUnit.MILLISECONDS); if (r != null) { return r; } else { log.warn(timeoutMillis + " ms timeout reached for IntraLineDiff" + " in project " + key.getProject().get() + " on commit " + key.getCommit().name() + " for path " + key.getPath() + " comparing " + key.getBlobA().name() + ".." + key.getBlobB().name() + ". Killing " + getName()); forcefullyKillThreadInAnUglyWay(); return Result.TIMEOUT; } } @SuppressWarnings("deprecation") private void forcefullyKillThreadInAnUglyWay() { try { stop(); } catch (Throwable error) { // Ignore any reason the thread won't stop. log.error("Cannot stop runaway thread " + getName(), error); } } private void shutdownGracefully() { if (!input.offer(Input.END_THREAD)) { log.error("Cannot gracefully stop thread " + getName()); } } @Override public void run() { try { for (;;) { Input in; try { in = input.take(); } catch (InterruptedException e) { log.error("Unexpected interrupt on " + getName()); continue; } if (in == Input.END_THREAD) { return; } Result r; try { r = new Result(IntraLineLoader.compute(in.key)); } catch (Exception error) { r = new Result(error); } if (!result.offer(r)) { log.error("Cannot return result from " + getName()); } } } catch (ThreadDeath iHaveBeenShot) { // Handle thread death by gracefully returning to the caller, // allowing the thread to be destroyed. } } private static class Input { static final Input END_THREAD = new Input(null); final IntraLineDiffKey key; Input(IntraLineDiffKey key) { this.key = key; } } static class Result { static final Result TIMEOUT = new Result((IntraLineDiff) null); final IntraLineDiff diff; final Exception error; Result(IntraLineDiff diff) { this.diff = diff; this.error = null; } Result(Exception error) { this.diff = null; this.error = error; } } } }